/*
 * CalibrateForm.java
 *
 * Created on 15 avril 2007, 13:17
 */

package courselog;

import courselog.maputils.CalibratedMap;
import courselog.maputils.TileMapDialog;
import courselog.maputils.CalibratedMapBrowser;
import ewe.filechooser.FileChooser;
import ewe.filechooser.ImageFileChooser;
import ewe.fx.Color;
import ewe.fx.IImage;
import ewe.fx.Image;
import ewe.fx.Rect;
import ewe.io.File;
import ewe.sys.mThread;
import ewe.ui.ControlConstants;
import ewe.ui.ControlEvent;
import ewe.ui.Event;
import ewe.ui.Form;
import ewe.ui.Gui;
import ewe.ui.Menu;
import ewe.ui.MenuEvent;
import ewe.ui.MenuItem;
import ewe.ui.MessageBox;

/**
 * Form for calibration of pictures.
 * The user can set the reference points, enter their coordinates 
 * or extract them from kml files.
 * @author rigal
 */
public class CalibrateForm extends CourseLogAppForm {
    
    /**
     * Right button for map actions.
     */
    protected PullDownForKey pdMapActions;
    /**
     * Left button for file and change form actions.
     */
    protected PullDownForKey pdFileActions;
    /**
     * Browser for the displayed calibrated map.
     */
    protected CalibratedMapBrowser cmb;
    
    /**
     * Calling application.
     */
    protected MainApplication myApp=null;
    
    /**
     * Current images directory.
     */
    public String stCurDir=null;
    
    /**
     * Set scale to 1.
     */
    protected MenuItem miScaleOne=new MenuItem("Rescale 1$R","res/scaleone.png",Color.White);
    /**
     * Find the positions of the reference points.
     */
    protected MenuItem miFindReference=new MenuItem("Find Ref.$F","res/questionmark.png",Color.White);
    /**
     * Split the current pictures into tiles.
     */
    protected MenuItem miSplitTiles=new MenuItem("Tile$T","res/tiles.png",null);
    /**
     * Save current calibrated map with the reference points.
     */
    protected MenuItem miSave=new MenuItem("Save image$S","ewe/savesmall.bmp",Color.White);
    
    /**
     * Constructor building the form and its menus.
     * @param myApp Calling application.
     */
    public CalibrateForm(MainApplication myApp) {
        this.myApp=myApp;
        setTitle("Calibrate - CourseLog");
        Form.globalIcon = myApp.myAppGlobalIcon;
        
        this.stCurDir=myApp.getMyMapDir();
        cmb= new CalibratedMapBrowser(myApp.myTr);
        Menu menuMapActions;
        miScaleOne.action="R";
        MenuItem miSeparator=new MenuItem("-");
        miFindReference.action="F";
        miSplitTiles.action="T";
        MenuItem miOpenKML=new MenuItem("Import KML path$K",
                "res/iconkmlearth.png",Color.Black);
        miOpenKML.action="K";
        MenuItem miOpen=new MenuItem("Open image$O","ewe/opensmall.bmp",Color.White);
        miOpen.action="O";
        miSave.action="S";
        MenuItem[] tmiMapActions=new MenuItem[]{miScaleOne,miSeparator,
        miFindReference,miSplitTiles,miOpenKML,miSeparator,miOpen,miSave};
        
        Menu menuFileActions;
        /** menu for actions */
        MenuItem miOptions=new MenuItem("Options$O","ewe/optionssmall.bmp",Color.White);
        miOptions.action="P";
        MenuItem miNavigate=new MenuItem("Navigate$N","res/iconnavigate.png",Color.White);
        miNavigate.action="N";
        MenuItem miDistance=new MenuItem("Distance$D","res/courselogsmall.png",Color.White);
        miDistance.action="D";
        MenuItem miConvert=new MenuItem("Convert$v","res/convertsmall.png",Color.White);
        miConvert.action="V";
        MenuItem miExit=new MenuItem("Exit$x","ewe/exitsmall.bmp",Color.White);
        miExit.action="X";
        MenuItem[] tmiFileActions=new MenuItem[]{miOptions,miNavigate,miDistance,
        miConvert,miSeparator,miExit};
        
        menuMapActions=new Menu(tmiMapActions,"Map actions");
        pdMapActions=new PullDownForKey("Actions",menuMapActions);
        
        menuFileActions=new Menu(tmiFileActions,"Goto panels");
        pdFileActions=new PullDownForKey("GOTO",menuFileActions);
        
        ActionKeysDispatcher akdM= new ActionKeysDispatcher();
        akdM.setLeftAndRight(pdFileActions,pdMapActions);
        pdFileActions.setActionKeyListener(akdM);
        pdMapActions.setActionKeyListener(akdM);
        
        courseLogAppFormSetup(pdFileActions,pdMapActions,cmb);
        cmb.setActionKeyDispatcher(akdM);
        //updateFormState();
    }
    
    /** Update the states of controls depending on the map state.
     */
    public void updateFormState() {
        if(cmb.cmMyMap==null) {
            miScaleOne.modifiers |= MenuItem.Disabled;
            miFindReference.modifiers |= MenuItem.Disabled;
            miSplitTiles.modifiers |= MenuItem.Disabled;
            miSave.modifiers |= MenuItem.Disabled;
        }else{
            miScaleOne.modifiers &= ~MenuItem.Disabled;
            if(cmb.isMapModified) {
                miSave.modifiers &= ~MenuItem.Disabled;
            }
            if(cmb.cmMyMap.vRefPoints.size()>1) {
                miFindReference.modifiers &= ~MenuItem.Disabled;
                miSplitTiles.modifiers &= ~MenuItem.Disabled;
            }else{
                miFindReference.modifiers |= MenuItem.Disabled;
                miSplitTiles.modifiers |= MenuItem.Disabled;
            }
        }
        pdMapActions.make(true);
    }
    
    // OVERIDDEN METHODS
    
    /**
     * Handle form event such as menu and keyboard events.
     * @param ev Incoming event.
     */
    public void onEvent(Event ev) {
        try {
            if((ev.target==dbgL.getLogControl())&&(ev.type==ControlEvent.FOCUS_IN)) {
                Gui.takeFocus(cmb,ByRequest);
            }
            if((ev.target==this)&&(ev.type==ControlEvent.FOCUS_IN)) {
                Gui.takeFocus(defC,ControlConstants.ByRequest);
                myApp.myNextActivePanel=-1;
            }
            if((ev.target == pdFileActions)||(ev.target==pdMapActions)) {
                if(ev.type==MenuEvent.SELECTED) {
//                CourseLogAppForm.dbgL.addToLog("\n pressed:"+ev.target+" item "+pdFileActions.getLastSelected());
                    MenuEvent mEv=(MenuEvent) ev;
                    final MenuItem selItem= (MenuItem) mEv.selectedItem;
//                CourseLogAppForm.dbgL.addToLog("\n selected:"+selItem.action);
                    mThread actThread= new mThread(new Runnable() {
                        public void run() {
                            executeAction(selItem.action);
                            //updateFormState();
                        }
                    });
                    actThread.start();
                }
            }
        } catch (NullPointerException ex) {
            CourseLogAppForm.dbgL.addExceptionToLog(ex);
        } catch (IndexOutOfBoundsException ex) {
            CourseLogAppForm.dbgL.addExceptionToLog(ex);
        } catch (ClassCastException ex) {
            CourseLogAppForm.dbgL.addExceptionToLog(ex);
        }
    }
    
    /**
     * Dispatch the action from a form button.
     * @param actString String associated to the pressed button to determine the action.
     */
    public void executeAction(String actString) {
        if(actString=="X") tryExitForm(-1);
        else if(actString=="P") tryGotoOptions();
        else if(actString=="N") tryGotoNavigate();
        else if(actString=="V") tryGotoConvert();
        else if(actString=="D") tryGotoDistance();
        else if(actString=="K") tryOpenKMLFile();
        else if(actString=="O") tryOpenFile();
        else if(actString=="S") trySaveFile();
        else if(actString=="R") rescaleOne();
        else if(actString=="F") findBestReference();
        else if(actString=="T") tileMap();
    }
    
    /** Go to the calibrate form */
    public void tryGotoOptions() {
        tryExitForm(MainApplication.ACTIVEOPTIONS);
    }
    
    /** Go to the calibrate form */
    public void tryGotoCalibrate() {
        tryExitForm(MainApplication.ACTIVECALIBRATION);
    }
    
    /** Go to the navigate form */
    public void tryGotoNavigate() {
        tryExitForm(MainApplication.ACTIVENAVIGATE);
    }
    
    /** Go to the distance log form */
    public void tryGotoDistance() {
        tryExitForm(MainApplication.ACTIVEDISTANCE);
    }
    
    /** Go to the Convert form */
    public void tryGotoConvert() {
        tryExitForm(MainApplication.ACTIVECONVERT);
    }
    
    /**
     * Triggers an exit from the application
     * @param activateCode Index of the next state of the application. <I>-1</I> means exit.
     */
    public void tryExitForm(int activateCode) {
        int dlgRes=MessageBox.IDYES;
        if(cmb.isMapModified){
            dlgRes=(new MessageBox("Exit without save",
                    "Map modifications are not saved.\nExit anyway?",
                    MessageBox.MBYESNO)).execute();
        }
        if(dlgRes==MessageBox.IDYES){
            this.close(Form.IDOK);
        }
        myApp.myNextActivePanel=activateCode;
    }
    
    /**
     * Opens a KML file to get position of reference points.
     */
    public void tryOpenKMLFile() {
        int resDlg= MessageBox.IDYES;
        if(cmb.isMapModified) {
            resDlg=(new MessageBox("Save current Map",
                    "Current map references are not saved.\nLose changes anyway?",
                    MessageBox.MBYESNO)).execute();
        }
        if(resDlg!=MessageBox.IDYES) return;
        
        FileChooser wpCh= new FileChooser(FileChooser.OPEN,stCurDir);
        wpCh.switchMultiSelect(false);
        resDlg= wpCh.execute();
        if(resDlg!=FileChooser.IDOK) return;
        
        File fWp=wpCh.getChosenFile();
        stCurDir=fWp.getFullPath();
        cmb.cmMyMap.initReferenceLocationsFromKML(fWp);
        cmb.isMapModified=true;
    }
    
    /**
     * Open a new picture file.
     * If reference points are stored in a file with the same, it is used.
     */
    public void tryOpenFile() {
//        CourseLogAppForm.dbgL.addToLog("\n tryOpenFile");
        int resDlg= MessageBox.IDYES;
        if(cmb.isMapModified) {
            resDlg=(new MessageBox("Save current Map",
                    "Current map references are not saved.\nLose changes anyway?",
                    MessageBox.MBYESNO)).execute();
        }
        if(resDlg!=MessageBox.IDYES) return;
        
        ImageFileChooser imgCh= new ImageFileChooser(ImageFileChooser.OPEN,stCurDir);
        resDlg= imgCh.execute();
        if(resDlg!=ImageFileChooser.IDOK) return;
        
        File fImg=imgCh.getChosenFile();
        stCurDir=fImg.getFullPath();
        Image im=new Image(imgCh.getChosen());
        cmb.setCalibratedMapFrom(fImg.getFullPath(),im);
        Gui.takeFocus(cmb,Gui.ByRequest);
    }
    
    /**
     * Save the picture file and the reference points.
     */
    public void trySaveFile() {
        if(!cmb.isMapModified) return;
        File fRef= new File(cmb.cmMyMap.getReferenceFileName());
        int resDlg= MessageBox.IDYES;
        if(fRef.exists()){
            resDlg=(new MessageBox("Overwrite Reference File",
                    "Current map reference file exists.\n"+
                    fRef.getName()+"\nOverwrite ?",
                    MessageBox.MBYESNO)).execute();
        }
        if(resDlg!=MessageBox.IDYES) return;
        
        if(cmb.cmMyMap.saveReferenceFile(fRef)) {
            cmb.isMapModified=false;
        };
        Gui.takeFocus(cmb,Gui.ByRequest);
    }
    
    /**
     * Rescale the picture to scale 1.
     */
    public void rescaleOne() {
        cmb.reCenter();
        Gui.takeFocus(cmb,Gui.ByRequest);
    }
    
    /**
     * Determine which pair of reference points generates the minimum error.
     * This also displays the reference points and the computed positions.
     */
    public void findBestReference() {
        int i1=cmb.cmMyMap.idxRP1;
        int i2=cmb.cmMyMap.idxRP2;
        cmb.cmMyMap.findOptimalRefPoint();
        if((i1!=cmb.cmMyMap.idxRP1)||(i2!=cmb.cmMyMap.idxRP2)) {
            cmb.isMapModified=true;
        }
        if(cmb.cmMyMap.idxRP1<0) {
            cmb.stateReferenceDisplay=CalibratedMapBrowser.STATE_DISPLAYSIMPLE;
        }else{
            cmb.stateReferenceDisplay=CalibratedMapBrowser.STATE_DISPLAYDOUBLE;
        }
        cmb.updateDisplayedImage();
        //cmb.repaint();
        Gui.takeFocus(cmb,Gui.ByRequest);
    }
    
    /**
     * Convert the current picture into a set of tiles.
     * The number of tiles is asked to the user.
     */
    public void tileMap() {
        if(cmb.cmMyMap.vRefPoints.size()<2) {
            MessageBox mb=new MessageBox("Missing calibration",
                    "This map has not enough reference points (<2)",MessageBox.MBOK);
            mb.execute();
            return;
        }
        
        TileMapDialog tmd=new TileMapDialog(this.cmb.baseIImage);
        int res=tmd.execute();
        if(res!=Form.IDOK) return;
        
        Rect rA=new Rect();
        CalibratedMap cm=cmb.cmMyMap;
        int mX=cm.imgW;
        int mY=cm.imgH;
        int dX=(int) (mX/tmd.valDivide);
        if(dX*tmd.valDivide<mX) dX++;
        int dY=(int) (mY/tmd.valDivide);
        if(dY*tmd.valDivide<mY) dY++;
        int iC;
        int oY;
        int dYcurrent;
        String nomF=cm.stMapFileName;
        File fImg=new File(nomF);
        int oX;
        oX=nomF.lastIndexOf('.');
        String baseName=nomF.substring(0,oX);
        CalibratedMap tmp=null;
        oX=0;
        int iL=0;
        while(oX<mX) {
            if((oX+dX)>mX) dX=mX-oX;
            iC=0;
            oY=0;
            rA.x=oX;
            rA.width=dX;
            dYcurrent=dY;
            while(oY<mY) {
                if((oY+dYcurrent)>mY) dYcurrent=mY-oY;
                rA.y=oY;
                rA.height=dYcurrent;
                String tileName=baseName+"_"+iL+"_"+iC+".png";
                //CalibrateForm.dbgL.addToLog("\nFn:"+tileName+" r("+oX+","+oY+"-"+dX+"x"+dYcurrent);
                if(!cmb.cmMyMap.saveSubMap(rA,tileName,tmp)) {
                    // Error, operation is interrupted
                    MessageBox mb=new MessageBox("Write error",
                            "Impossible to write tile file :\n"+
                            tileName,MessageBox.MBOK);
                    mb.execute();
                    return;
                }
                iC++;
                oY += dYcurrent;
            }
            iL++;
            oX += dX;
        }
    }
}